/** @file    SessionProtocolDefault.cpp
 *  @author  Alessandro Polo
 *  @version $Id: SessionProtocolDefault.cpp 2883 2010-08-09 14:51:56Z alex $
 *  @brief
 * File containing methods for the wosh::communication::SessionProtocolDefault class.
 * The header for this class can be found in SessionProtocolDefault.h, check that file
 * for class description.
 ****************************************************************************/
/* Copyright (c) 2007-2010, WOSH - Wide Open Smart Home 
 * by Alessandro Polo - OpenSmartHome.com
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the OpenSmartHome.com WOSH nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Alessandro Polo ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Alessandro Polo BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ****************************************************************************/

 #include <framework/communication/SessionProtocolDefault.h>
 #include <core/Session.h>
 #include <core/ObjectAllocator.h>
 #include <core/Containers.h>
 #include <core/List.h>
 #include <core/Request.h>
 #include <core/Response.h>
 #include <core/Property.h>
 #include <core/Notification.h>
 #include <core/Utilities.h>
 #include <core/MethodsCommon.h>
 #include <core/WoshKernel.h>
 #include <core/NetworkManager.h>
 #include <core/UserManager.h>
 #include <framework/persistence/plaintext/EncoderProtocolPlainText.h>
 #include <framework/communication/TextMessage.h>
 #include <stdarg.h>
 #include <stdio.h>

 #define _ENDLINE	"\r\n"
 
using namespace std;
namespace wosh {
 using namespace communication;
 namespace communication {

 WOSH_REGISTER(wosh::communication::SessionProtocolDefault, "wosh::SessionProtocol", 1.0, _static_SessionProtocolDefault )

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS

SessionProtocolDefault::SessionProtocolDefault()
	: SessionProtocol() {
	// setup default aliases
	this->aliases["methods"] = new ShellCommand( _METHOD_ListMethods );
	this->aliases["props"] = new ShellCommand( _METHOD_ListProperties );
	this->aliases["interfaces"] = new ShellCommand( _METHOD_ListInterfaces );
	this->aliases["requirements"] = new ShellCommand( _METHOD_ListRequirements );

	this->aliases["set"] = new ShellCommand( _METHOD_SetProperty );
	this->aliases["get"] = new ShellCommand( _METHOD_GetProperty );

 }

SessionProtocolDefault::~SessionProtocolDefault() {
	this->aliases.clear();
 }

//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

std::string SessionProtocolDefault::buildPrompt( Session* session ) const {
	if ( session == NULL ) return "";
	return Utilities::format("%s@%s# ", session->getUsername().c_str(), session->getCurrentLocation().toString().c_str() );
  }

WRESULT SessionProtocolDefault::parseInput( const std::string& dataline, Session* session ) {
	if ( session == NULL ) return WRET_ERR_PARAM;
	ShellCommand curr_cmd;
	if ( dataline == "." ) {
		curr_cmd = this->lastCommand;
	 }
	else {
		if ( this->aliases.find(dataline) != this->aliases.end() ) {
			ShellCommand* refCmd = this->aliases[dataline];
			curr_cmd = *refCmd;
		 }
		else if ( !curr_cmd.fromString(dataline ) ) {
			printMessage( session, "  Error: can't parse string" _ENDLINE );
			return WRET_ERR_PARAM;
		 }
	 }
	if ( this->aliases.find(curr_cmd.getMainCommand()) != this->aliases.end() ) {
		ShellCommand* refCmd = this->aliases[curr_cmd.getMainCommand()];
		curr_cmd.setMainCommand( refCmd->getMainCommand() );
	 }
	this->lastCommand = curr_cmd;
	WRESULT internal_cmd = parseShellCommand( &curr_cmd, session );
	if ( WSUCCEEDED(internal_cmd) )
		return WRET_OK;

	Request* request = curr_cmd.toRequest();
	if ( request == NULL ) {
		printMessage( session, "  Error: can't create Message -Request" _ENDLINE );
		return WRET_ERR_INTERNAL;
	 }
	Message* message = new Message(request);
	message->setDestination( session->getCurrentLocation() );
	message->setDestinationBus( session->getCurrentBus() );
	return postMessage( message, session );
 }

WRESULT SessionProtocolDefault::parseShellCommand( ShellCommand* command, Session* session ) {
	if ( session == NULL ) return WRET_ERR_PARAM;
	if ( command == NULL ) return WRET_ERR_PARAM;

	if ( command->getMainCommand() == "help" ) {
		printMessage( session,	_ENDLINE "WOSH Shell Protocol [wosh::communication::SessionProtocolDefault,"_SessionProtocolDefault_VERSION"]" \
								_ENDLINE "Internal Commands:" _ENDLINE \
								"\t help    : print this screen" _ENDLINE \
								"\t man     : show manual" _ENDLINE \
								"\t session : print session info" _ENDLINE \
								"\t whoami  : print username" _ENDLINE \
								"\t su      : switch user" _ENDLINE \
								"\t echo    : reply input" _ENDLINE \
								"\t proto   : change session protocol" _ENDLINE \
								"\t notify  : notify a message to user(s)" _ENDLINE \
								"\t pwd     : print current path" _ENDLINE \
								"\t ck      : change kernel" _ENDLINE \
								"\t cd      : change destination (location)" _ENDLINE \
								"\t cb      : change bus (target)" _ENDLINE \
								"\t home    : change to home URI" _ENDLINE \
								"\t scd     : (smart) change destination (name)" _ENDLINE \
								"\t methods : list available methods of current location" _ENDLINE \
								"\t props   : list available properties of current location" _ENDLINE \
								"\t get     : retrieve a Property" _ENDLINE \
								"\t set     : set a Property" _ENDLINE \
								"\t which   : find method" _ENDLINE \
								"\t .       : repeat last command-line" _ENDLINE \
								"\t exit    : close session" _ENDLINE _ENDLINE
							);
	 }
	//---------------------------------------------------------------------------------------------------------
	else if ( command->getMainCommand() == "exit" || command->getMainCommand() == "logout" ) {

	 }
	//---------------------------------------------------------------------------------------------------------
	else if ( command->getMainCommand() == "whoami" ) {
		if ( session->getUsername() == session->getUsernameLogged() )
			vprintMessage( session, "  %s"_ENDLINE, session->getUsername().c_str() );
		else
			vprintMessage( session, "  %s [logged as %s]"_ENDLINE, session->getUsername().c_str(), session->getUsernameLogged().c_str() );
	 }
	//---------------------------------------------------------------------------------------------------------
	else if ( command->getMainCommand() == "hello" ) {
		vprintMessage( session, "  Hi %s"_ENDLINE, session->getUsername().c_str() );
	 }
	//---------------------------------------------------------------------------------------------------------
	else if ( command->getMainCommand() == "su" ) {
		WRESULT ret = session->su( command->getArgument(0).toString() );
		if ( ret == WRET_ERR_SECURITY )
			printMessage( session, "  Command not allowed." );
		else if ( WFAILED(ret) )
			printMessage( session, "  Command Error." );
		else
			vprintMessage( session, "  Working as %s"_ENDLINE, session->getUsername().c_str() );
	 }
	//---------------------------------------------------------------------------------------------------------
	else if ( command->getMainCommand() == "home" ) {
		session->changeToUserHome();
	 }
	//---------------------------------------------------------------------------------------------------------
	else if ( command->getMainCommand() == "echo" ) {
		printMessage( session, command->toString(false) );
	 }
	//---------------------------------------------------------------------------------------------------------
	else if ( command->getMainCommand() == "session" ) {
		printMessage( session, _ENDLINE "Session Information:" _ENDLINE \
								"\tID :\t\t" + Utilities::toString(session->getID()) + _ENDLINE \
								"\tUser :\t\t" + session->getUsername() + _ENDLINE \
								"\tPath :\t\t" + session->getCurrentLocation().toString() + _ENDLINE _ENDLINE
							);
	 }
	//---------------------------------------------------------------------------------------------------------
	else if ( command->getMainCommand() == "proto" ) {
		if ( !command->getArgument(0).isStringNotEmpty() )
			vprintMessage( session, "  SessionProtocol :\t%s %g"_ENDLINE, this->getClassName(), 0.0 );
		else {
		//	WRESULT ret = this->parent->changeSessionProtocol( &session, command->getArgument(0).asString() );
		//	if ( WSUCCEEDED(ret) )
		//		printMessage( session, Utilities::format("  SessionProtocol :\t%s"_ENDLINE, command->getArgument(0).asString().c_str()) );
		//	else
		//		printMessage( session, Utilities::format("  ERROR#%d %s:"_ENDLINE, ret, this->getProtocolName().c_str()) );
		 }
	 }
	//---------------------------------------------------------------------------------------------------------
	else if ( command->getMainCommand() == "pwd" ) {
		if ( session->getCurrentLocation().isEmpty() )
			printMessage( session, "  /" );
		else
			vprintMessage( session, "  %s"_ENDLINE, session->getCurrentLocation().toString().c_str() );
	 }
	//---------------------------------------------------------------------------------------------------------
	else if ( command->getMainCommand() == "ck" ) {
		string target = "";
		if ( !command->getArgument(0).isStringNotEmpty() ) {
			vprintMessage( session, "  Kernel: %s"_ENDLINE, session->getCurrentLocation().getKernelName().c_str() );
			return WRET_OK;
		 }
		else if ( command->getArgument(0).asString() == "." )
			target = WoshKernel::getKernelName();
		else {
			target = command->getArgument(0).asString();
			if ( !WoshKernel::getInstance()->network().isHost(target) )
				printMessage( session, "  Warning: Host seems invalid!" );
		 }
		URI newUri = session->getCurrentLocation();
		newUri.setKernelName( target );
		session->setCurrentLocation(newUri);
	 }
	//---------------------------------------------------------------------------------------------------------
	else if ( command->getMainCommand() == "cb" ) {
		if ( !command->getArgument(0).isStringNotEmpty() ) {
			session->setCurrentBus(_Bus_ANY);
			printMessage( session, "  Current Bus: " _Bus_ANY " (default)" );
		 }
		else {
			session->setCurrentBus( command->getArgument(0).asString() );
			vprintMessage( session, "  Current Bus: %s"_ENDLINE, session->getCurrentBus().c_str() );
		 }
	 }
	//---------------------------------------------------------------------------------------------------------
	else if ( command->getMainCommand() == "cd" ) {
		if ( command->getArgument(0).isEmpty() ) {
			vprintMessage( session, "  %s"_ENDLINE, session->getCurrentLocation().toString().c_str() );
			return WRET_OK;
		 }

		string target = command->getArgument(0).toString();
		if ( target == "@" ) { // index selection mode, enter the i-entry of of current location's children
			long index = command->getArgument(1).toLong(-1);
			List uri_list;
			URI childWild(session->getCurrentLocation(), "*");
			long count = URICollector::getInstance().list( &childWild, false, uri_list.getVector() );
			if ( count == 0 ) {
				printMessage( session, "  Error: No children (or unable to list)." );
				return WRET_OK;
			 }
			if ( index < 0 || index >= uri_list.size() ) {
				printMessage( session, "  Error: specify valid numeric index." );
		//		renderList(session, &uri_list, "Known Children:" );
				return WRET_OK;
			 }
			URI* sel_child = dynamic_cast<URI*>(uri_list.itemAt(index));
			if ( sel_child == NULL ) {
				printMessage( session, "  Error: Intenral error retrieving selected uri." );
				return WRET_OK;
			 }
			session->setCurrentLocation(*sel_child);
			uri_list.clear();
			return WRET_OK;
		 }

		URI newUri = session->getCurrentLocation();
		if ( target.find("/") == 0 ) {
			newUri.clearPathName();
			target = target.substr(1);
		 }
		if ( target == ".." ) {
			if ( newUri.getPath().empty() )
				newUri.setName("");
			else {
				newUri.setName(newUri.getPath().back());
				newUri.getPath().pop_back();
			 }
		 }
		else {
			if ( !newUri.getName().empty() )
				newUri.getPath().push_back(newUri.getName());
			newUri.setName(target);
			if ( !newUri.isValid() )
				printMessage( session, "  Warning: URI seems invalid!" );
			else {
				if ( newUri.isKernelLocal() && !newUri.exists() )
					printMessage( session, "  Warning: URI seems invalid or not found" );
			 }
		 }
		session->setCurrentLocation(newUri);
	 }
	//---------------------------------------------------------------------------------------------------------
	else if ( command->getMainCommand() == "notify" ) {
		if ( !command->getArgument(0).isStringNotEmpty() ) {
			printMessage( session, "  Error: Please enter target user!" );
			return WRET_ERR_PARAM;
		 }
		else {
			if ( !UserManager::isUser(command->getArgument(0).asString()) ) {
				vprintMessage( session, "  Error: Invalid target user '%s'"_ENDLINE, command->getArgument(0).asString().c_str() );
				return WRET_ERR_PARAM;
			 }
			if ( command->getArgCount() < 2 || !command->getArgument(1).isStringNotEmpty() ) {
				printMessage( session, "  Error: Please enter valid message!" );
				return WRET_ERR_PARAM;
			 }
			wosh::communication::TextMessage* txtmsg = new wosh::communication::TextMessage();
			txtmsg->setSender_User( session->getUsernameLogged() );
			txtmsg->setRecipent_User( command->getArgument(0).asString() );
			txtmsg->setProtocol( Notification::PROTOCOL_HUMAN  );
			txtmsg->setPlain( command->getArgument(1).asString() );
			Message* message = new Message(txtmsg);
			message->setDestinationBroadcast(true);
			printMessage( session, "  Notification Sent." );
			return postMessage( message, session );
		 }
	 }
	else {
		return WRET_ERR_PARAM;
	}


	return WRET_OK;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT SessionProtocolDefault::renderMessage( const Message* message, const Bus*, Session* session ) {
	if ( session == NULL ) return WRET_ERR_PARAM;
	if ( message == NULL ) return WRET_ERR_PARAM;
	if ( message->isEmpty() ) return WRET_OK;

	string source = "";
	if ( message->getSource() != session->getCurrentLocation() )
		source = message->getSource().toString() + " :: ";

	if ( message->getContent()->isResponse() ) {
		const Response* response = message->getContent()->asResponse();
		if ( response == NULL ) return WRET_ERR_PARAM;
		if ( WFAILED(response->getReturnValue()) ) {
			vprintMessage( session, "  %sMethod: %s returned ERROR#%d",
									source.c_str(), response->getMethod().c_str(), response->getReturnValue() );
			if ( response->hasData() ) {
				stringstream sbuffer;
				wosh::persistence::plaintext::EncoderProtocolPlainText::serializeTo(response->getData(), sbuffer, true);
				printMessage( session, sbuffer.str() );
			 }
			return WRET_OK;
		 }
		if ( !response->hasData() ) {
			vprintMessage( session, "  %sMethod: %s returned OK"_ENDLINE,
									source.c_str(), response->getMethod().c_str(), response->getReturnValue() );
			return WRET_OK;
		 }
		stringstream sbuffer;
		sbuffer << Utilities::format("  %sResponse of '%s' : OK [%s]"_ENDLINE, source.c_str(), response->getMethod().c_str(), response->getData()->getClassName() );
		wosh::persistence::plaintext::EncoderProtocolPlainText::serializeTo(response->getData(), sbuffer, true);
		printMessage( session, sbuffer.str() );
		return WRET_OK;
	 }

	stringstream sbuffer;
	sbuffer << Utilities::format("  %sMessage#"PRId64", Content:"_ENDLINE, source.c_str(), message->getID() );
	wosh::persistence::plaintext::EncoderProtocolPlainText::serializeTo(message->getContent(), sbuffer, true);
	printMessage( session, sbuffer.str() );
	return WRET_OK;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

void SessionProtocolDefault::vprintMessage( Session* session, const char* format, ... ) {
	if ( session == NULL ) return;
	///@bug ...sooo lame
	char* msgbuffer = new char[1023];
	memset(msgbuffer,'\0',1023);
	va_list list;
	va_start(list, format );
#ifdef _OS_WINCE
	vsprintf(msgbuffer,format, list);
#elif defined(WIN32_VS)
	vsprintf_s(msgbuffer, 1023, format, list);
#else
	vsprintf(msgbuffer,format, list);
#endif
	va_end(list);
	string temp(msgbuffer);
	delete [] msgbuffer;
	printMessage( session, temp );
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

 }; // namespace communication
}; // namespace wosh
